// Object.defineProperty可以检测到对象的变化，我们可以写下下面的代码。
function defineReactive(data, key, val) {
  Object.defineProperty(data, key, {
    configurable: true,
    enumerable: true,
    writable: true,
    get: function () {
      return val;
    },
    set: function (newVal) {
      if (newVal == val) {
        return
      }
      val = newVal;
    }
  })
}

// defineReactive函数对于Object.defineProperty进行封装，每当从data中取对应的key就会触发get函数，往data中的key设置值的时候就会触发set函数。
// 总结起来就是在get中收集依赖，在set中触发依赖。

// 依赖收集在哪里？？
// 首先我们每个key都应该有一个依赖。假设依赖保存在window.target中

function defineReactiveDep(data, key, val) {
  let Dep = []
  Object.defineProperty(data, key, {
    configurable: true,
    enumerable: true,
    writable: true,
    get: function () {
      Dep.push(window.target)   //进行依赖收集
      return val
    },
    set: function (newVal) {
      if (newVal == val) {
        return
      }
      for (let i = 0; i < Dep.length; i++) {
        Dep[i](newVal, val);   //触发依赖
      }
      val = newVal;
    }
  })
}


// 上面的代码对于收集依赖有点耦合
// 我们可以封装一下

export default class Dep {
  constructor() {
    this.subs = []
  }
  addSub(sub) {
    this.subs.push(sub);
  }
  removeSub(sub) {
    remove(this.subs, sub);
  }
  depend() {
    if (window.target) {
      this.addSub(window.target);
    }
  }
  notify() {
    const subs = this.subs.slice()
    for (let i = 0; i < subs.length; i++) {
      subs[i].update();
    }
  }
}

function remove(arr, item) {
  if (arr.length) {
    const index = arr.indexOf(item);
    if (index > -1) {
      arr.splice(index, 1);
    }
  }
}


// 接下来改造以下  defineReactiveDep
function defineReactiveDep(data, key, val) {
  // let Dep = []
  let Dep = new Dep()
  Object.defineProperty(data, key, {
    configurable: true,
    enumerable: true,
    writable: true,
    get: function () {
      //  Dep.push(window.target)
      Dep.depend(window.target)   //进行依赖收集
      return val;
    },
    set: function (newVal) {
      if (newVal == val) {
        return
      }
      // for (let i = 0; i < Dep.length; i++) {
      //   Dep[i](newVal, val)   //触发依赖
      // }
      Dep.notify();
      val = newVal;
    }
  })
}

// 思考？？？依赖是谁，现在我们假设的是window.target。当我们的属性发生变化之后，通知谁。
// 我们要使用数据的地方很多，而且类型也可能不一样。我们需要一个类，来将这些情况封装起来。

// 下面代码需要用到的函数
let bailRE = /[^\w.$]/
function parsePath(path) {
  if (bailRE.test(path)) {
    return;
  }
  const segments = path.split('.')
  return function (obj) {
    for (let i = 0; i < segments.length; i++) {
      if (!obj) return;
      obj = obj[segments[i]];
    }
    return obj;
  }
}

export class Watcher {
  constructor(vm, exportFn, cb) {
    this.vm = vm;
    // 执行this.getter。可以读取到data.a.b.c的内容
    this.getter = parsePath(exportFn);
    this.cb = cb;
    this.value = this.get();
  }
  get() {
    window.target = this;
    let value = this.getter.call(this.vm, this.vm);
    window.target = undefined;
    return value;
  }
  update() {
    const oldValue = this.value;
    this.value = this.get();
    this.cb.call(this.vm, this.value, oldValue);
  }
}

// 递归监测所有key  这个类的主要作用就是一个数据内的所有属性（包括子属性）都转换成getter/setter函数


/**
 * Observer类会附加到每一个被侦测的object中。
 * 一旦被附加上，Observer会将Object的所有属性转换为getter/setter的形式
 * 来收集属性的依赖，并且当属性发生变化时会通知这些依赖 
 */
export class Observer {
  constructor(value) {
    this.value = value
    if (!Array.isArray(value)) {
      this.walk(value)
    }
  }
  /**
   * walk会将每个属性都转换成getter/setter的形式来侦测变化
   * 这个方法只对Object时会被调用
   */
  walk(obj) {
    const keys = Object.keys(obj)
    for (let i = 0; i < keys.length; i++) {
      defineReactiveDep(obj, obj[i], obj[keys[i]])
    }
  }
}

// 增加递归子属性  {a: c也会被监测到 {c: 1} }
function defineReactiveDep(data, key, val) {
  // let Dep = []
  // 增加递归子属性
  if (typeof val === 'object') {
    new Observer(val)
  }
  let Dep = new Dep();
  Object.defineProperty(data, key, {
    configurable: true,
    enumerable: true,
    writable: true,
    get: function () {
      //  Dep.push(window.target)
      Dep.depend(window.target)   //进行依赖收集
      return val
    },
    set: function (newVal) {
      if (newVal == val) {
        return
      }
      // for (let i = 0; i < Dep.length; i++) {
      //   Dep[i](newVal, val)   //触发依赖
      // }
      Dep.notify()
      val = newVal
    }
  })
}